import re
import os
import sys
from ivic.vsched import Model
from ivic.core import Config, project_path
from ivic.core import Logging
logger = Logging.get_logger('ivic.vsched.Utils')

def get_config():
    return Config.load(project_path('/etc/ivic/vsched.yaml'))

def load_sqlite_testdb(reload = False):
    database_file = 'test_vsched.db'
    connect_string = 'sqlite:///%s' % database_file

    if reload:
        logger.info('delete database %s', database_file)
        try:
            os.remove(database_file)
        except:
            pass

    session = Model.get_session(connect_string)
    logger.info('create database on %s', connect_string)
    if reload:
        Model.Base.metadata.create_all(session.bind)
    return session

def load_mysql_testdb(reload = False):
    connect_string = 'mysql://civic:123456@localhost/liqin_cvc'
    if reload:
        #os.system("mysqldump -ucivic -p123456 -hlocalhost iVIC_Portal_development")
        os.system("mysqladmin --force -ucivic -p123456 -hlocalhost drop liqin_cvc")
        os.system("mysqladmin -ucivic -p123456 -hlocalhost create liqin_cvc")
        os.system("mysql -ucivic -p123456 -hlocalhost liqin_cvc < test/testdb.sql")
    return Model.get_session(connect_string)

def load_config_db():
    from ivic.core import Config
    connect_string = get_config()['connect_string']
    return Model.get_session(connect_string)

def load_portal_db():
    connect_string = 'mysql://civic:123456@portal.ivic.org.cn/iVIC_Portal_development'
    return Model.get_session(connect_string)
    
def cmd_test_model(db = 'sqlite', expected_count = {}):
    if db == 'portal':
        s = load_portal_db()
    elif db == 'mysql':
        s = load_mysql_testdb()
    elif db == 'sqlite':
        s = load_sqlite_testdb()
    else:
        s = load_config_db()

    for cls in Model.MapperClasses:
        count = s.query(cls).count()
        if count:
            print '%s table has %d records' % (cls, count)
        if expected_count.has_key(cls) and count != expected_count[cls]:
            raise Exception, 'test model error: count(%s) is %d, but should be %d' % (cls.__name__, count, expected_count[cls])

def cmd_import_sample(db = 'sqlite'):
    if db == 'mysql':
        s = load_mysql_testdb()
    elif db == 'config':
        s = load_config_db()
    else:
        s = load_sqlite_testdb()

    user = Model.User()
    user.password = '123456'
    user.email = 'liqin@act.buaa.edu.cn'
    s.add(user)

    vmc1 = Model.VirtualMachineContainer()
    vmc1.address = '192.168.4.140'
    vmc1.status = 'online'
    vmc1.capability = 'vmc'
    s.add(vmc1)

    vmc2 = Model.VirtualMachineContainer()
    vmc2.address = '192.168.4.160'
    vmc2.status = 'online'
    vmc2.capability = 'vmc'
    s.add(vmc2)

    vmc3 = Model.VirtualMachineContainer()
    vmc3.address = '192.168.4.180'
    vmc3.status = 'online'
    vmc3.capability = 'vmc,vsr'
    s.add(vmc3)

    vsr1 = Model.VirtualMachineContainer()
    vsr1.address = '192.168.4.150'
    vsr1.capability = 'vsr'
    vsr1.status = 'online'
    s.add(vsr1)

    s.flush()
    logger.debug('added user: %s', user)
    logger.debug('added vmc1: %s', vmc1)
    logger.debug('added vmc2: %s', vmc2)

def cmd_import_xml(xml_type):
    if xml_type == 'vl':
        xmlfile = 'doc/vlab-sample.xml'
    elif xml_type == 'vc':
        xmlfile = 'doc/vcluster-sample.xml'
    elif xml_type == 'vt':
        xmlfile = 'doc/vtemp-sample.xml'
    else:
        xmlfile = xml_type

    xml = file(xmlfile).read()
    
    s = load_sqlite_testdb()
    ret = Model.import_xml(xml, s)
    logger.debug('import_xml returns %s', ret)

def cmd_export_xml(key):
    s = load_sqlite_testdb()
    str = Model.export_xml_id(key, s)
    print str

def tablename_to_classname(str):
    tokens = str.split('_')
    
    last = tokens[-1]
    if last == 'vswitches': #FIXME bery bery ugly!!! 
        tokens[-1] = 'vswitch'
    elif last == 'communities':
        tokens[-1] = 'community'
    elif last[-1:] == 's':
        tokens[-1] = last[:-1]
        
    capitalize = lambda x: x[0].upper() + x[1:] 
    return ''.join(map(capitalize, tokens))

def generate_type(field_name, field_type):
    if field_name == 'id' and field_type == 'int(11)':
        return 'Integer, primary_key = True'
    elif field_type == 'int(11)':
        bad_foreign_keys = ['owner_id', 'admin_role_id', 'user_role_id', 'config_id', 'depend_task_id']
        if field_name in bad_foreign_keys or field_name[-3:] != '_id':
            return 'Integer'
        if field_name == 'vswitch_id':
            tname = 'vswitches'
        else:
            tname = field_name[:-3] + 's'
        return 'Integer, ForeignKey(\'%s.id\')' % tname
    elif field_type == 'datetime':
        if field_name == 'created_at':
            return 'DateTime, default=datetime.utcnow'
        elif field_name == 'updated_at':
            return 'DateTime, default=datetime.utcnow, onupdate=datetime.utcnow'
        else:
            return 'DateTime'
    elif field_type == 'text,':
        return 'Text'
    elif field_type == 'tinyint(1)':
        return 'Boolean'
        #return 'Unknown'
    elif field_type[:7] == 'varchar':
        return field_type.replace('varchar', 'String')
    else:
        return 'Unknown'# + field_type
    
def cmd_import_model(sqlfile = 'ivic.vsched/model.sql', outfile = sys.stdout):
    if type(outfile) == str:
        outfile = file(outfile, 'w')
    
    print >> outfile, """from datetime import datetime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Table, Column, Integer, String, Text, DateTime, MetaData, ForeignKey, Boolean
from sqlalchemy.orm.attributes import InstrumentedAttribute

Base = declarative_base()
"""
    table_header = re.compile('^CREATE TABLE.*`(.*)`.*\\(')
    table_footer = re.compile('^\\) ENGINE.*;')
    
    parse_field = False
    for l in file(sqlfile).read().splitlines():
        mat = re.match(table_header, l) 
        if mat:
            table_name = mat.groups()[0]
            parse_field = True
            has_primary_key = False
            out_lines = []
            out_lines.append('class %s(Base):' % tablename_to_classname(table_name))
            out_lines.append('    __tablename__ = \'%s\'' % table_name)
            out_lines.append('')
            continue
        
        mat = re.match(table_footer, l)
        if mat:
            table_name = ''
            parse_field = False
            
            if not has_primary_key:
                continue
            
            for ol in out_lines:
                print >> outfile, ol
            print >> outfile
            continue
        
        if parse_field:
            tokens = l.split()
            if tokens[0][0] != '`':
                continue
            field_name = tokens[0][1:len(tokens[0]) - 1]
            field_type = tokens[1]
            gen_type = generate_type(field_name, field_type)
            if gen_type == 'Unknown':
                continue
            elif gen_type == 'Integer, primary_key = True':
                has_primary_key = True
            invalid_names = ['metadata']
            if field_name in invalid_names:
                out_lines.append('    field_%s = Column(\'%s\', %s)' % (field_name, field_name, gen_type))
            else:
                out_lines.append('    %s = Column(%s)' % (field_name, gen_type))

def cmd_reload_model(db = 'sqlite'):
    if db == 'sqlite':
        load_sqlite_testdb(reload = True)
    elif db == 'mysql':
        load_mysql_testdb(reload = True)

def cmd_dump_model(filename):
    os.system("mysqldump -d -ucivic -p123456 -hportal.ivic.org.cn iVIC_Portal_development --compact | sed 's/AUTO_INCREMENT[^ ]* //' > %s" % filename)

def cmd_scheduler_test():
    cmd_reload_model('mysql')
    cmd_import_sample('mysql')
    cmd_impl('importTemplate', file('doc/vlab-sample.xml').read())
    cmd_impl('deployV_ByKey', 'vLab@1')
    cmd_impl('importTemplate', file('doc/vcluster-sample.xml').read())
    cmd_impl('deployV_ByKey', 'vCluster@1')
    cmd_scheduler()
    cmd_impl('startV_ByKey', 'vLab@1')
    cmd_impl('startV_ByKey', 'vCluster@1')
    cmd_scheduler()
    cmd_impl('stopV_ByKey', 'vLab@1')
    cmd_impl('stopV_ByKey', 'vCluster@1')
    cmd_scheduler()
    cmd_impl('undeployV_ByKey', 'vLab@1')
    cmd_impl('undeployV_ByKey', 'vCluster@1')
    cmd_scheduler()

    cmd_impl('redeployV_ByKey', 'vLab@1')
    cmd_impl('redeployV_ByKey', 'vCluster@1')
    cmd_scheduler()
    cmd_impl('startV_ByKey', 'vLab@1')
    cmd_impl('startV_ByKey', 'vCluster@1')
    cmd_scheduler()
    #cmd_impl('stopV_ByKey', 'vLab@1')
    #cmd_impl('stopV_ByKey', 'vCluster@1')
    #cmd_scheduler()
    
def cmd_regression_test(count = 1):
    if type(count) == str:
        count = int(count)
    
    modelsql = 'doc/portal-model.sql'
    importedmodel = '/tmp/ImportedModel.py'
    #cmd_dump_model(modelsql)
    cmd_import_model(modelsql, importedmodel)
    
    cmd_reload_model()
    cmd_import_sample()
    for i in [0]:
        cmd_import_xml('vl')
        cmd_import_xml('vc')
        cmd_import_xml('vt')
    for i in [0]:
        cmd_export_xml('vCluster@%d' % (i + 1))
        cmd_export_xml('vLab@%d' % (i + 1))
        cmd_export_xml('vTemplate@%d' % (i + 1))
    
    from glob import glob
    from ivic.vsched import Model
    bad_xmls = glob('test/*.xml')
    for f in bad_xmls:
        try:
            logger.info('try importing bad xml %s', f)
            cmd_import_xml(f)
            raise Exception, 'no exception raise while importing bad xml'
        except Model.XmlImportError, e:
            logger.info('catched expected error: %s', e.message)

    expected_count = {
        Model.VmTemp: 2,
        Model.User: 1,
        Model.VlabTempsVmTemp: 8,
        Model.VclusterTempsVmTemp: 2,
        Model.VlabTemp: 1,
        Model.VclusterTemp: 1
    }
    cmd_test_model('sqlite', expected_count)
    
    S = Model.get_ScopedSession('sqlite:///test_vsched.db')
    s = S()
    
    do_impl(s, 'listTemplate')
    types = ['vLab', 'vCluster', 'vTemplate']
    type_result = {'vLab':1 + 1, 'vCluster':1 + 1, 'vTemplate':2 + 1}
    for t in types:
        ret = do_impl(s, 'listTemplate_ByType', t)
        logger.debug('assert %d == %d', len(ret.splitlines()), type_result[t])
        assert len(ret.splitlines()) == type_result[t]

    try:
        ret = do_impl(s, 'listTemplate_ByType', 'notexist')
        raise Exception, 'no exception raise while listTemplate with bad type %s' % t
    except:
        pass
    
    keys = ['vLab@1', 'vCluster@1', 'vTemplate@1', 'vTemplate@2']
    for k in keys:
        ret = do_impl(s, 'showTemplate_ByKey', k)
    
    bad_keys = [ 'vLab@11', 'vLab@a', 'vlab@1', 'v@1']
    for k in bad_keys:
        try:
            do_impl(s, 'showTemplate_ByKey', k)
            raise Exception, 'no exception raise while showTemplate with bad key %s' % k
        except:
            pass
    
    uuids = ['c', 'e', '3', '5']
    for u in uuids:
        do_impl(s, 'showTemplate', u)
    
    bad_uuids = [ 'a', '0', 'ce', '355']
    for u in bad_uuids:
        try:
            do_impl(s, 'showTemplate', u)
            raise Exception, 'no exception raise while showTemplate with bad uuid %s' % s
        except:
            pass

    for i in range(count):
        retval = do_impl(s, 'deployV', 'c')
        assert retval == 1 + i
        retval = do_impl(s, 'listInstance')
        assert len(retval.splitlines()) == 5 * (i + 1) + 1

    for i in range(count, 0, -1):
        do_impl(s, 'removeInstance_ByKey', 'vCluster@%d' % i)
        retval = do_impl(s, 'listInstance')
        assert len(retval.splitlines()) == 5 * (i - 1) + 1

    for i in range(count):
        retval = do_impl(s, 'deployV', 'e')
        assert retval == count + 1 + i
        retval = do_impl(s, 'listInstance')
        assert len(retval.splitlines()) == 22 * (i + 1) + 1

    for i in range(count, 0, -1):
        do_impl(s, 'removeInstance_ByKey', 'vLab@%d' % i)
        retval = do_impl(s, 'listInstance')
        assert len(retval.splitlines()) == 22 * (i - 1) + 1

    for i in range(count):
        do_impl(s, 'deployV', 'e')
        do_impl(s, 'deployV', 'c')
        retval = do_impl(s, 'listInstance')
        assert len(retval.splitlines()) == 27 * (i + 1) + 1
    do_impl(s, 'removeInstances', '')
    retval = do_impl(s, 'listInstance')
    assert len(retval.splitlines()) == 1

    for i in range(count):
        do_impl(s, 'deployV', 'e')
        do_impl(s, 'deployV', 'c')
        retval = do_impl(s, 'listInstance')
        assert len(retval.splitlines()) == 27 * (i + 1) + 1
    do_impl(s, 'removeTemplate', 'e')
    do_impl(s, 'removeTemplate', 'c')
    retval = do_impl(s, 'listInstance')
    assert len(retval.splitlines()) == 1
    retval = do_impl(s, 'listTemplate')
    assert len(retval.splitlines()) == 3

    do_scheduler(S)

    #FIXME sqlite has db lock issue
    #cmd_import_xml('vl')
    #do_impl(s, 'deployV', 'e')
    #cmd_import_xml('vc')
    #do_impl(s, 'deployV', 'c')
    #do_scheduler(S)

    #do_impl(s, 'startV_ByKey', 'vLab@1')
    #do_impl(s, 'startV_ByKey', 'vCluster@1')
    #do_scheduler(S)

    #do_impl(s, 'stopV_ByKey', 'vLab@1')
    #do_impl(s, 'stopV_ByKey', 'vCluster@1')
    #do_scheduler(S)

    #do_impl(s, 'undeployV_ByKey', 'vLab@1')
    #do_impl(s, 'undeployV_ByKey', 'vCluster@1')
    #do_scheduler(S)

    #do_impl(s, 'redeployV_ByKey', 'vLab@1')
    #do_impl(s, 'redeployV_ByKey', 'vCluster@1')
    #do_scheduler(S)

    #do_impl(s, 'startV_ByKey', 'vLab@1')
    #do_impl(s, 'startV_ByKey', 'vCluster@1')
    #do_scheduler(S)

    print 'Success! All test passed!'

def do_impl(session, operation, *args):
    from ivic.vsched.Impl import Impl
    from ivic.core.Utils import string_trim
    impl = Impl(session)
    try:
        logger.debug('calling %s(%s)', operation, ', '.join(map(string_trim, args)))
        op = getattr(impl, operation)
        retval = op(*args)
        logger.debug('%s returns %d lines', operation, len(str(retval).splitlines()))
        return retval
    except AttributeError, e:
        print dir(impl)
        logger.exception(e)
        raise LookupError, 'no such operation %s' % operation
    except TypeError, e:
        print 'wrong agument, see comamnd help:\n', op.__doc__
        #logger.exception(e)
        raise

def cmd_impl(operation, *args):
    session = load_config_db()
    #session = load_sqlite_testdb()
    retval = do_impl(session, operation, *args)
    print retval,

def cmd_server(n = 1):
    try:
        n = int(n)
    except:
        n = 1

    session = load_config_db()
    from ivic.vsched.Server import Server
    svr = Server(session)
    svr.serverLoop(num_request = n)

def cmd_proxy(server, operation, *args):
    from SOAPpy import SOAPProxy
    url = 'http://%s:8181' % server
    proxy = SOAPProxy(url)
    op = getattr(proxy, operation)
    retval = op(*args)
    print retval,

def do_scheduler(Session):
    config = get_config()

    from ivic.vsched.Controller import JobDispatcher
    jobdispatcher = JobDispatcher(
        Session, 
        thread_num = config['thread_num'],
        debug_soap = config['debug_soap'],
        exit_when_no_job = 1
    )

    jobdispatcher.start()
    print 'waiting for jobdispatcher'
    jobdispatcher.join()
    print 'exit'

def cmd_scheduler():
    config = get_config()
    Session = Model.get_ScopedSession(config['connect_string'])
    do_scheduler(Session)

def do_task(task):
    from ivic.vsched.ContainerOp import SOAPProxy
    print task
    content = eval(task.content)
    proxy = SOAPProxy(content['url'])
    invoke_op = getattr(proxy, content['op_name'])
    invoke_op(*content['op_args'])

def cmd_redotask(id):
    config = get_config()
    from ivic.vsched.Model import Task
    session = Model.get_session(config['connect_string'])
    id = int(id)
    task = session.query(Task).get(id)
    do_task(task)

def cmd_redojob(id):
    from ivic.vsched import Config
    from ivic.vsched.Model import Task
    session = Model.get_session(Config.connect_string)
    id = int(id)
    for task in session.query(Task).filter(Task.job_id == id):
        do_task(task)

def cmd_massacre():
    from ivic.vsched import Model
    from ivic.vsched import Config
    from ivic.vsched.ContainerOp import SOAPProxy, get_vmc_url, get_vsr_url
    session = Model.get_session(Config.connect_string)
    for vmi in session.query(Model.VirtualMachineInstance):
        if vmi.virtual_machine_container_id and vmi.uuid:
            vmc = session.query(Model.VirtualMachineContainer).get(vmi.virtual_machine_container_id)
            if vmc and vmc.address:
                try:
                    proxy = SOAPProxy(get_vmc_url(vmc.address))
                    proxy.stopVM(vmi.uuid)
                    proxy.undeployVM(vmi.uuid)
                except Exception, e:
                    logger.exception(e)
        session.delete(vmi)
    session.flush()

    for vswitch in session.query(Model.Vswitch):
        if vswitch.virtual_machine_container_id and vswitch.uuid:
            vsr = session.query(Model.VirtualMachineContainer).get(vswitch.virtual_machine_container_id)
            if vsr and vsr.address:
                try:
                    proxy = SOAPProxy(get_vsr_url(vsr.address))
                    proxy.stopVSwitch(vswitch.uuid)
                    proxy.deregisterVSwitch(vswitch.uuid)
                except Exception, e:
                    logger.exception(e)
        session.delete(vswitch)
    session.flush()

    for table in [Model.Nic, Model.VlabInstance, Model.VirtualClusterInstance, Model.VclusterTemp, Model.Job, Model.Task]:
        for record in session.query(table):
            session.delete(record)
        session.flush()

def usage():
    print """Usage: %s cmd [options]
       ivic-vsched-utils regression_test [<num>]

       ivic-vsched-utils dump_model [<filename>]

       ivic-vsched-utils import_model [<sqlfile>] [<modelpy>]
                               <sqlfile> default to 'ivic/ivic-vsched/model.sql'
                               <modelpy> default to standard output
                               
       ivic-vsched-utils reload_model
                               
       ivic-vsched-utils import_sample

       ivic-vsched-utils import_xml vc|vl|vt|<filename>
                               vc for 'doc/vcluster-sample.xml'
                               vl for 'doc/vlab-sample.xml'
                               vt for 'doc/vtemplate-sample.xml'

       ivic-vsched-utils export_xml vLab@<num>|vTemplate@<num>|vCluster@<num>
 
       ivic-vsched-utils test_model [remote|local]
                               default to remote
       ivic-vsched-utils impl <operation>
       ivic-vsched-utils server
       ivic-vsched-utils proxy <server>[:<port>] <operation>       
""" % sys.argv[0]

def filename_to_content(str):
    if len(str) == 0 or str[0] != ':':
        return str
    try:
        return file(str[1:]).read()
    except:
        return str

def main():
    import logging
    logging.basicConfig()
    if len(sys.argv) > 1:
        cmd = 'cmd_' + sys.argv[1]
        if not globals().has_key(cmd):
            logger.error('no function %s', cmd)
            return
        func = globals()[cmd]
        args = map(filename_to_content, sys.argv[2:])
        func(*args)
    else:
        usage()
